/*
 * License GNU LGPL
 * Copyright (C) 2012 Amrullah <amrullah@panemu.com>.
 */
package com.abc.cheque.control;


import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.ParseException;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TextField;

import com.abc.cheque.common.TiwulFXUtil;

/**
 *
 * @author Amrullah <amrullah@panemu.com>
 */
public class NumberField<T extends Number> extends TextField {

    private DecimalFormat formatter = TiwulFXUtil.getDecimalFormat();
    private boolean ignore;
    private int maxLength = 10;
    private ObjectProperty<T> value = new SimpleObjectProperty<>();
    private String regex = "";
    private Class<T> clazz;
    private boolean grouping = false;
    public NumberField() {
        this((Class<T>) Double.class);
    }

    public NumberField(Class<T> clazz) {
        this.clazz = clazz;
        textProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> ov, String s, String s1) {
                if (ignore) {
                    return;
                }
                ignore = true;
                if (maxLength > -1 && s1.length() > maxLength) {
                    setText(s1.substring(0, maxLength));
                }

                if (getRegex() != null && !getRegex().equals("") && !s1.matches(getRegex())) {
                    setText(s);
                }
                if (getText().isEmpty() || getText().equals(formatter.getDecimalFormatSymbols().getDecimalSeparator())) {
                    setText("");
                    value.set(null);
                } else {
                    value.set(castToExpectedType(getText()));
                }
                ignore = false;
            }
        });
        focusedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> ov, Boolean oldValue, Boolean newValue) {
                formatter.applyPattern(getPattern(!newValue && grouping));
                ignore = true;
                if (value.get() != null) {
                    setText(formatter.format(value.get()));
                } else {
                    setText("");
                }
                ignore = false;
            }
        });
        if (clazz != null) {
            setupRegex();
        }
    }

    protected String getRegex() {
        return regex;
    }

    private void setupRegex() {
        if (clazz.equals(Integer.class) || clazz.equals(Long.class)) {
            regex = "-?\\d*";
            return;
        }
        char separator = TiwulFXUtil.getDecimalFormat().getDecimalFormatSymbols().getDecimalSeparator();
        if (separator == '.') {
            regex = "-?\\d*\\.?\\d*";
        } else {
            regex = "-?\\d*" + separator + "?\\d*";
        }
    }

    public final void setNumberType(Class<T> clazz) {
        this.clazz = clazz;
        setupRegex();
        formatter.setParseBigDecimal(clazz.equals(BigDecimal.class));
    }

    /**
     * Cast string to Number type that accepted by this NumberField.
     * It depends on NumberType (see {@link #setNumberType} 
     * @param numberString
     * @return 
     */
    public T castToExpectedType(String numberString) {
        if (numberString.length() == 1 && numberString.charAt(0) == formatter.getDecimalFormatSymbols().getDecimalSeparator()) {
            return null;
        }
        try {
            Number number = formatter.parse(numberString);
            T val = (T) number;
            if (number instanceof Long || number instanceof Integer) {
                if (clazz.equals(BigDecimal.class)) {
                    val = (T) new BigDecimal(number.longValue());
                } else if (clazz.equals(Double.class)) {
                    val = (T) new Double(number.doubleValue());
                } else if (clazz.equals(Float.class)) {
                    val = (T) new Float(number.floatValue());
                } else if (clazz.equals(Integer.class) && number instanceof Long) {
                    val = (T) new Integer(number.intValue());
                }
            }
            return val;
        } catch (ParseException ex) {
            throw new RuntimeException(ex);
        }
    }

    public void setGrouping(boolean grouping) {
        this.grouping = grouping;
        formatter.applyPattern(getPattern(grouping));
    }

    public boolean isGrouping() {
        return grouping;
    }

    protected String getPattern(boolean grouping) {
        if (clazz.equals(Integer.class) || clazz.equals(Long.class)) {
            if (grouping) {
                formatter.setParseBigDecimal(clazz.equals(BigDecimal.class));
                return "###,###";
            } else {
                return "###";
            }
        } else {
            if (grouping) {
                formatter.setParseBigDecimal(clazz.equals(BigDecimal.class));
                return "###,##0.00";
            } else {
                return "##0.00";
            }
        }
    }

    public ReadOnlyObjectProperty<T> valueProperty() {
        return value;
    }

    public void setValue(T value) {
        this.value.set(value);
        ignore = true;
        if (value != null) {
            formatter.applyPattern(getPattern(isGrouping()));
            setText(formatter.format(value));
        } else {
            setText("");
        }
        ignore = false;
    }

    public T getValue() {
        return value.get();
    }

    public void setMaxLength(int maxLength) {
        this.maxLength = maxLength;
    }

    public int getMaxLength() {
        return maxLength;
    }
}
